#include "bq76920.h"

void BQ76920_TS1_SetOutMode(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_AHB1PeriphClockCmd(BQ76920_TS1_Clk, ENABLE);

    GPIO_InitStructure.GPIO_Pin   = BQ76920_TS1_Pin;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_OUT;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_NOPULL;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_Init(BQ76920_TS1_Port, &GPIO_InitStructure);
}

/**
 * @brief 从BQ76920低功耗模式唤醒
 *
 */
void BQ76920_Wakeup(void)
{
    MCU_WAKE_BQ_ONOFF(1);
    Delay_ms(100);
    MCU_WAKE_BQ_ONOFF(0);
}

void BQ76920_EntryShip(void)
{
    BQ76920_WriteRegisterWithCRC(SYS_CTRL1, 0x19);
    Delay_ms(20);
    BQ76920_WriteRegisterWithCRC(SYS_CTRL1, 0x1a);
}

/**
 * @brief get ADC gain and offset
 *
 * @param bq_data
 */
void BQ76920_GetADCGainOffset(BQ76920_t *bq_data)
{
    uint8_t gain[2];
    uint8_t adc_gain;

    gain[0]         = BQ76920_ReadRegister(ADCGAIN1); // ADC_GAIN1
    gain[1]         = BQ76920_ReadRegister(ADCGAIN2); // ADC_GAIN2
    adc_gain        = ((gain[0] & 0x0c) << 1) | ((gain[1] & 0xe0) >> 5);
    bq_data->GAIN   = 365 + adc_gain;
    bq_data->OFFSET = BQ76920_ReadRegister(ADCOFFSET);
}

void BQ76920_OCDAndSCDProtect(void)
{
    uint8_t protect1_reg;
    uint8_t protect2_reg;

    protect1_reg = BQ76920_ReadRegister(PROTECT1);
    protect2_reg = BQ76920_ReadRegister(PROTECT2);

    protect1_reg = protect1_reg | (SCD_100us_delay << 3);
    protect1_reg = protect1_reg | SCD_Threshold_89mV;
    protect2_reg = protect2_reg | (OCD_160ms_delay << 4);
    protect2_reg = protect2_reg | OCD_Threshold_8mV;

    BQ76920_WriteRegisterWithCRC(PROTECT1, protect1_reg);
    BQ76920_WriteRegisterWithCRC(PROTECT2, protect2_reg);
}

void BQ76920_OVAndUVProtect(BQ76920_t *bq_data)
{
    uint8_t protect3_reg;
    uint8_t ov_trip_full;
    uint8_t uv_trip_full;
    uint16_t temp;

    protect3_reg = BQ76920_ReadRegister(PROTECT3);
    protect3_reg = protect3_reg | (UV_Delay_4s << 6);
    protect3_reg = protect3_reg | (OV_Delay_4s << 4);
    BQ76920_WriteRegisterWithCRC(PROTECT3, protect3_reg);

    temp         = (uint16_t)((float)(TLB_OV_PROTECT * 1000 - bq_data->OFFSET) / ((float)(bq_data->GAIN) / 1000));
    ov_trip_full = ((temp & 0x0ff0) >> 4) & 0xff;

    temp         = (uint16_t)((float)(TLB_UV_PROTECT * 1000 - bq_data->OFFSET) / ((float)(bq_data->GAIN) / 1000));
    uv_trip_full = ((temp & 0x0ff0) >> 4) & 0xff;

    BQ76920_WriteRegisterWithCRC(OV_TRIP, ov_trip_full);
    BQ76920_WriteRegisterWithCRC(UV_TRIP, uv_trip_full);
}

void BQ76920_Init(BQ76920_t *bq_data)
{
    uint8_t reg_data;

    BQ76920_TS1_SetOutMode();
    BQ76920_Wakeup();

    // Clear all fault bits
    reg_data = 0xff;
    BQ76920_WriteRegisterWithCRC(SYS_STAT, reg_data);

    // Disable all cell balancing
    reg_data = 0x00;
    BQ76920_WriteRegisterWithCRC(CELLBAL1, reg_data);

    // Enable ADC(Bit 4 = 1)
    // Select external temperature sensor(Bit 3 = 1)
    reg_data = 0x18;
    BQ76920_WriteRegisterWithCRC(SYS_CTRL1, reg_data);

    // Enable the coulomb counter(Bit 6 = 1)
    // Enable the Discharge FET(Bit 1 = 1)
    // Enable the Charge FET(Bit 0 = 1)
    reg_data = 0x43;
    BQ76920_WriteRegisterWithCRC(SYS_CTRL2, reg_data);

    // For optimal performance, should be programmed to 0x19
    reg_data = 0x19;
    BQ76920_WriteRegisterWithCRC(CC_CFG, reg_data);

    BQ76920_GetADCGainOffset(bq_data);
    BQ76920_OCDAndSCDProtect();
    BQ76920_OVAndUVProtect(bq_data);
}

void BQ76920_GetCellVoltage(BQ76920_t *bq_data)
{
    uint32_t reg_data[2];
    uint8_t reg_addr;
    uint16_t adc_val;
    uint8_t i;

    reg_addr = VC1_HI;
    for (i = 0; i < BQ76920_CELL_NUM; i++)
    {
        if (i == 2 && BQ76920_CELL_NUM == 3)
        {
            reg_addr += 4;
        }
        else if (i == 3 && BQ76920_CELL_NUM == 4)
        {
            reg_addr += 2;
        }

        reg_data[0] = BQ76920_ReadRegister(reg_addr);
        reg_data[1] = BQ76920_ReadRegister(reg_addr + 1);

        // printf("reg_data[0]=%d\r\n", reg_data[0]);
        // printf("reg_data[1]=%d\r\n", reg_data[1]);

        adc_val                 = (reg_data[0] << 8) | reg_data[1];
        bq_data->cellVoltage[i] = (adc_val * bq_data->GAIN) / 1000 + bq_data->OFFSET; // in mV
        reg_addr += 2;
    }
}

void BQ76920_GetPackVoltage(BQ76920_t *bq_data)
{
    uint8_t reg_data[2];
    uint16_t adc_val;

    reg_data[0]          = BQ76920_ReadRegister(BAT_HI);
    reg_data[1]          = BQ76920_ReadRegister(BAT_LO);
    adc_val              = (reg_data[0] << 8) | reg_data[1];
    bq_data->packVoltage = 4 * adc_val * bq_data->GAIN / 1000 + BQ76920_CELL_NUM * bq_data->OFFSET; // in mV
}

void BQ76920_GetCurrent(BQ76920_t *bq_data)
{
    uint8_t reg_data[2];
    int16_t cc_val;
    float temp;

    reg_data[0] = BQ76920_ReadRegister(CC_HI);
    reg_data[1] = BQ76920_ReadRegister(CC_LO);

    cc_val = (reg_data[0] << 8) | reg_data[1];

    // printf("cc_val=%d\r\n", cc_val);

    if (cc_val & 0x8000)
    {
        cc_val = -(~cc_val + 1); // correcting
    }

    if (cc_val == 1 || cc_val == -1)
    {
        cc_val = 0;
    }

    temp = (cc_val * 8.44f) / 4; // in mA
    // bq_data->current = temp;

    // correcting
    if (temp > 0)
    {
        bq_data->current = -4.957e-05f * temp * temp + 0.7629f * temp - 1.329f; // in mA;
    }
    else
    {
        bq_data->current = 4.957e-05f * temp * temp + 0.7629f * temp + 1.329f; // in mA;
    }

    if (temp == 0)
    {
        bq_data->current = 0;
    }
}

void BQ76920_GetTsTemp(BQ76920_t *bq_data)
{
    float Rt = 0;
    float Rp = 10000;
    float T2 = 273.15 + 25;
    float Bx = 3380;
    float Ka = 273.15;
    uint8_t reg_data[2];
    int32_t TempRes;

    reg_data[0]     = BQ76920_ReadRegister(TS1_HI);
    reg_data[1]     = BQ76920_ReadRegister(TS1_LO);
    TempRes         = (reg_data[0] << 8) | reg_data[1];
    TempRes         = (10000 * (TempRes * 382 / 1000)) / (3300 - (TempRes * 382 / 1000));
    bq_data->TsTemp = 1 / (1 / T2 + (log(TempRes / Rp)) / Bx) - Ka + 0.5;
}

void BQ76920_PrintInfo(BQ76920_t *bq_data)
{
    uint8_t i;

    for (i = 0; i < BQ76920_CELL_NUM; i++)
    {
        printf("Vcell%d:%dmV\r\n", i + 1, bq_data->cellVoltage[i]);
    }

    printf("Vpack:%dmV\r\n", bq_data->packVoltage);
    printf("current:%.2fmA\r\n", bq_data->current);
    printf("TsTemp:%.2fC\r\n", bq_data->TsTemp);
}

void BQ76920_GetState(BQ76920_t *bq_data)
{
    uint8_t reg_data        = BQ76920_ReadRegister(SYS_CTRL2);
    bq_data->dischargeState = reg_data & 0x02;
    bq_data->chargeState    = reg_data & 0x01;
}

void BQ76920_OpenCHG(BQ76920_t *bq_data)
{
    BQ76920_GetState(bq_data);
    if (bq_data->dischargeState != 0)
    {
        BQ76920_WriteRegisterWithCRC(SYS_CTRL2, 0X43);
    }
    else
    {
        BQ76920_WriteRegisterWithCRC(SYS_CTRL2, 0X41);
    }
}

void BQ76920_CloseCHG(BQ76920_t *bq_data)
{
    BQ76920_GetState(bq_data);
    if (bq_data->dischargeState != 0)
    {
        BQ76920_WriteRegisterWithCRC(SYS_CTRL2, 0X42);
    }
    else
    {
        BQ76920_WriteRegisterWithCRC(SYS_CTRL2, 0X40);
    }
}

void BQ76920_OpenDSG(BQ76920_t *bq_data)
{
    BQ76920_GetState(bq_data);
    if (bq_data->chargeState != 0)
    {
        BQ76920_WriteRegisterWithCRC(SYS_CTRL2, 0X43);
    }
    else
    {
        BQ76920_WriteRegisterWithCRC(SYS_CTRL2, 0X42);
    }
}

void BQ76920_CloseDSG(BQ76920_t *bq_data)
{
    BQ76920_GetState(bq_data);
    if (bq_data->chargeState != 0)
    {
        BQ76920_WriteRegisterWithCRC(SYS_CTRL2, 0X41);
    }
    else
    {
        BQ76920_WriteRegisterWithCRC(SYS_CTRL2, 0X40);
    }
}

void BQ76920_OpenDSGAndCHG(void)
{
    BQ76920_WriteRegisterWithCRC(SYS_CTRL2, 0X43);
}

void BQ76920_CloseDSGAndCHG(void)
{
    BQ76920_WriteRegisterWithCRC(SYS_CTRL2, 0X40);
}
